package com.glink.gateway.filters;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.ConvertUtils;
import com.auth0.jwt.interfaces.Claim;
import com.glink.common.contants.BaseConstants;
import com.glink.common.enums.ResponseCodeEnum;
import com.glink.common.exception.GeneralSimpleException;
import com.glink.common.util.BaseCommonUtils;
import com.glink.common.vo.ResponseResult;
import com.glink.gateway.common.*;
import com.glink.gateway.service.authorize.AuthService;
import com.glink.gateway.service.authorize.WhiteListService;
import com.glink.gateway.service.user.UserService;
import com.glink.gateway.vo.UserVO;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.Map;
import java.util.Objects;

import static com.glink.gateway.common.JwtUtil._BEARER;


/**
 * 登录认证和权限验证模块
 *
 * @author rhl
 * @since 2022/6/7 15:57
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory implements GlobalFilter {
    
    @Autowired
    WhiteListService whiteListService;

    @Autowired
    RedisUtil redisUtil;
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private AuthService authService;
    
    @Autowired
    private ReplayProperties replayProperties;
    
    @Value("${replay.validator:false}")
    private boolean replayValidator;
    
    /**
     * @param uri
     * @return true: 是白名单; false:不是白名单
     */
    private boolean checkWhiteList(URI uri) {
        return whiteListService.match(uri.getRawPath());
    }

    /**
     * 跨系统的白名单
     * @param uri 请求地址
     * @return 结果
     */
    private boolean checkCrossSystemWhiteList(URI uri) {
        return whiteListService.crossSystemMatch(uri.getRawPath());
    }


    /**
     * 鉴权实现
     * 1. 请求地址在白名单内
     * a. 直接转发请求到后端服务
     * 2. 请求地址不在白名单内
     * a. 获取token，获取用户信息
     * 获取成功->添加用户信息到header（userInfo），转发请求到后端服务
     * 获取失败 -> 返回前端code 300018，前端判断回到登录页面
     *
     * @return
     */
    private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, ResponseCodeEnum responseCodeEnum) {
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        ResponseResult<Object> responseResult = ResponseResult.failResult(responseCodeEnum.getStatus(), responseCodeEnum.getMessage());
        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSON.toJSONString(responseResult).getBytes());
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        
        log.info("1.网关鉴权开始>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //请求地址在白名单内
        ServerHttpRequest request = exchange.getRequest();
        String requestUrl = request.getURI().getRawPath();
        if (checkWhiteList(request.getURI())) {
            log.info("2.1.请求注意请求地址 {} 白名单访问，无需鉴权！", requestUrl);
            return chain.filter(exchange);
        } else {
            log.info("2.2.请求注意请求地址 {} 为非白名单访问需要鉴权！", requestUrl);
        }

        ServerHttpResponse response = exchange.getResponse();
        if(replayValidator){
            // 2.禁止重放验证
            try {
                AntiReplayValidator.builder()
                        .url(requestUrl)
                        .timestamp(ConvertUtils.toLong(HttpUtils.getHeaderVal(request,replayProperties.getHeaderKey().getTimestamp())))
                        .execute();
            } catch (GeneralSimpleException e) {
                return getVoidMono(response, ResponseCodeEnum.REPEAT_REQUEST_ERROR);
            }
        }

        //判断token是否为空
        String token = request.getHeaders().getFirst(HeaderConstants.TOKEN);
        if (StringUtils.isBlank(token)) {
            token = request.getQueryParams().getFirst(HeaderConstants.TOKEN);
            if (StringUtils.isBlank(token)) {
                log.error("3.token为空 ,是错误的");
                return getVoidMono(response, ResponseCodeEnum.TOKEN_MISSION);
            }
        }

        //验证jwtToken 是否正常
        //去掉Bearer
        token = StringUtils.trim(StringUtils.replace(token, _BEARER, ""));
        log.info("4.你的Token {}", token);
        boolean b = false;
        try {
            b = JwtUtil.verify(token);
            if (!b) {
                return getVoidMono(response, ResponseCodeEnum.TOKEN_EXPIRED);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("5.JWT Token 验证出现错误 ", e);
            return getVoidMono(response, ResponseCodeEnum.VERIFY_TOKEN_FAIL);
        }
        b = JwtUtil.isJwtExpired(token);
        if (b) {
            return getVoidMono(response, ResponseCodeEnum.TOKEN_EXPIRED);
        }
        Map<String, Claim> map = JwtUtil.parseJWT(token);
        String userName = map.getOrDefault(JwtUtil.LOGIN_NAME, null).asString();
        String randomOld = map.getOrDefault(JwtUtil.RANDOM, null).asString();

        String key = BaseConstants.REDIS_USER_TOKEN_PREFIX + userName + "_" + randomOld;
        //获取用户信息是否为空
        Object obj = redisUtil.get(key);
        if (Objects.isNull(obj)) {
            log.error("6.redis找不到用户信息 key：{}", key);
            return getVoidMono(response, ResponseCodeEnum.TOKEN_EXPIRED);
        }
        
        UserVO userVO = userService.findByUserName(userName);
        if (Objects.isNull(userVO)) {
            log.error("7.用户account[{}]不存在 ", userName);
            return getVoidMono(response, ResponseCodeEnum.USER_NOT_FOUND);
        }

        boolean status = authService.checkOrg(userVO);
        if(!status){
            return getVoidMono(response, ResponseCodeEnum.AUTH_ORG);
        }
        
        status = authService.checkRole(userVO);
        if(!status){
            return getVoidMono(response, ResponseCodeEnum.AUTH_ROLE);
        }
        String remoteIp = "";
        try {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if(Objects.nonNull(requestAttributes)){
                HttpServletRequest httpServletRequest = ((ServletRequestAttributes)requestAttributes).getRequest();
                remoteIp = BaseCommonUtils.getIP(httpServletRequest);
            }
        }catch (Exception e){
            log.error("000000：获取请求IP失败", e);
        }
        
        status = authService.checkAuthUrlByUserRole(userVO, requestUrl, remoteIp);
        if(!status){
            return getVoidMono(response, ResponseCodeEnum.AUTH_URL);
        }

        log.info("11. 请求token为：{}, 请求用户信息：account[{}] ", token, userName);
        request = request.mutate().
                 header("userName", userName)
                .header("userId", userVO.getId())
                .header("orgId", userVO.getOrgId())
                .header(JwtUtil.RANDOM, randomOld)
                .build();
        return chain.filter(exchange.mutate().request(request).build());
    }
 

    @Data
    @Accessors(chain = true)
    @Builder
    private static final class HasApiPermissionDTO {
        private Boolean result;
        private ResponseCodeEnum responseCode;
    }

}
